use std::{cmp::PartialOrd, fmt::Display};

pub fn learn_generics() {
  // _learn_generics0();
  // _learn_longest0();
  // _learn_longest1();
  _learn_lifetime_struct0();
}

fn _largest<T: PartialOrd>(list: &[T]) -> &T {
  let mut largest = &list[0];
  for item in list {
    if item > largest {
      largest = item;
    }
  }
  largest
}

fn _learn_generics0() {
  let number_list = vec![34, 50, 25, 100, 65];
  let result = _largest(&number_list);
  println!("The largest number is {}", result);
  let char_list = vec!['y', 'm', 'a', 'q'];
  let result = _largest(&char_list);
  println!("The largest char is {}", result);
}

// 类似于当因为有多种可能类型的时候必须注明类型,某些时候也需要注明生命周期的关系来确保
// 运行时实际使用的引用绝对是有效的.
// 生命周期的主要目标是避免悬垂引用.
fn _learn_lifetime0() {
  let r: &i32; // 外部作用域
  {
    let x = 5;
    //r = &x; // `x`活得不够长
  } // x离开作用域,被清理
    // r 仍在作用域内, r的作用域大于x,r存在得更久
    //println!("r:{}",r); // 尝试使用已离开作用域的引用
}

// 函数中的生命周期
/*
缺少生命周期说明符.这个函数的返回类型包含一个借来的值,但是签名没有说明它是从' x '还是' y '借来的.
注:因为这只是定义,不知道传递给函数的具体值,所以也不知道到底是if还是else会被执行.
也不知道传入的引用的具体的生命周期,所以也无法通过观察作用域来确定返回的引用是否总是有效.
借用检查器自身同样无法确定,因为它不知道x和y的生命周期是如何与返回值的生命周期相关联的.
*/
// fn _longest0(x:&str,y:&str)->&str{

// 生命周期注解
// 生命周期注解并不改变任何引用的生命周期的长短.它们描述了多个引用生命周期相互的关系,而不影响其生命周期.
// 与当函数签名中指定了范型类型参数后就可以接受任何类型一样,当指定了范型生命周期后,函数也能接受任何生命周期的引用.
// 单个的生命周期注解本身没有多少意义,因为生命周期注解告诉rust多个引用的泛型生命周期参数如何相互联系的.
// 期望函数签名表达如下限制: x,y这两个参数和返回的引用存活的一样久.(两个)参数和返回的引用的生命周期是相关的.
fn _longest0<'a>(x: &'a str, y: &'a str) -> &'a str {
  // 对于某些生命周期'a,函数会获取两个参数,它们都是与生命周期'a存在的一样长的字符串slice.
  // 函数会返回一个同样也与生命周期'a存在的一样长的字符串slice.它的实际含义是:
  //  longest函数返回的引用的生命周期与函数参数所引用的值的生命周期的较小者一致.
  // 这些关系就是我们希望rust分析代码时所使用的.
  if x.len() > y.len() {
    x
  } else {
    y
  }
  // 记住,通过在函数签名中指定生命周期参数时,我们并没有改变任何传入值或返回值的生命周期,而是指出
  // 任何不满足这个约束条件的值都将被借用检查器拒绝.注意 longest 函数并不需要知道 x 和 y 具体
  // 会存在多久,而只需要知道有某个可以被'a替代的作用域将会满足这个签名.

  // 当在函数中使用生命周期注解时,这些注解出现在函数签名中,而不存在于函数体中的任何代码中.生命周期
  // 注解成为了函数约定的一部分,非常像签名中的类型.
}

fn _learn_longest0() {
  let s1 = String::from("abcd");
  let s2 = "xyzojbk";
  let r = _longest0(s1.as_str(), s2);
  println!("lifetime-r:{r}");
}

// 当具体的引用被传递给 longest 时,被 'a 所替代的具体生命周期是 x的作用域和y的作用域相重叠的
// 那一部分.换一种说法就是泛型生命周期'a的具体生命周期等同于x和y的生命周期中较小的那一个.因为
// 我们用相同的生命周期参数'a标注了返回的引用值,所以返回的引用值就能保证在x和y中较短的那个生命周期
// 结束之前保持有效.
fn _learn_longest1() {
  let s1 = String::from("long string");
  {
    let s2 = String::from("xyz");
    // _longest0返回的引用值的作用域与s3一样
    let s3 = s1.as_str();
    let s4 = s2.as_str();
    // 通过传递不同具体生命周期的引用来限制 longest 函数的使用.
    let r = _longest0(s3, s4);
    println!("s3:{},s4:{}", s3, s4);
    println!("The longest string is:{}", r);
  } // s2,r 离开作用域后无效
} // s1 离开作用域后无效

fn _learn_longest2() {
  let s1 = String::from("long string");
  //let result;
  {
    let s2 = String::from("xyz");
    let s3 = s1.as_str();
    // s2 编译报错: s2活得不够久;说明result引用的生命周期必须是两个参数中较短的那个
    // let s4 = s2.as_str();
    // result = _longest0(s3, s4);
  }
  // 尝试在s2离开作用域后使用result
  // println!("The longest is:{}", result);
}

// 当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配.如果
// 返回的引用没有指向任何一个参数,那么唯一的可能就是它指向一个函数内部创建的值.
// 这将是一个悬垂引用,因为它将在函数结束时离开作用域.
/*
fn _longest_err<'a>(x:&str, y:&str)->&'a str{
  let r = String::from("hello");
  r.as_str()
}
*/
// 综上,生命周期是用于将函数的多个参数与其返回值的生命周期进行关联.一旦它们形成了某种关系,rust
// 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为.

// 结构体定义中的生命周期注解
// 结构体实例的作用域应该小于等于其中字段引用的作用域
struct ImportantExcerpt<'a> {
  part: &'a str,
}

fn _learn_lifetime_struct0() {
  let novel = String::from("Call me Ishmael. Some years ago...");
  let first_sentence = novel.split('.').next().unwrap();
  let i = ImportantExcerpt {
    part: first_sentence,
  };
  println!("{}", i.part);
}

// 个人总结:
// 1,正常代码中,数据引用的作用域小于数据的作用域;
// 2,函数中,作为返回值的引用的作用域应大于入参中作用域最小的那一个
// 3,结构体中,字段引用的作用域应大于结构体实例的作用域.
// struct instance < field ref
// return ref > min(arg1, arg2)
// data > data ref

// 生命周期的省略规则
// 1,为每一个引用参数都分配一个生命周期
// fn f(a:&T, b:&U) => fn f(a:&'a T, b:&'b U)
// 2,如果只有一个输入生命周期,那么它被赋予所有输出生命周期
// fn f(a:&T) -> &R => fn f(a:&'a T) -> &'a R
// 3,如果方法有多个输入生命周期并且其中一个参数是&self或&mt self,则所有输出生命周期被赋予self的生命周期.
// fn f(&self, b:&T) -> &R => fn f(&'a self, b:&'b T) -> &'a R

// 结合范型类型参数,trait bounds和生命周期的例子
fn _longest_with_an_announcement<'a, T>(
  // x,y的生命周期都被标记为'a
  x: &'a str,
  y: &'a str,
  // ann的类型必须实现了Display特征,才能利用println!输出
  ann: T, // 返回值的生命周期也被标注为'a, 表示返回值的生命周期至少比x,y中生命周期较短的那个更长
) -> &'a str
where
  T: Display,
{
  println!("{}", ann);
  if x.len() > y.len() {
    x
  } else {
    y
  }
}
